package com.opages.mvc.adapter.common.properties;

import com.opages.mvc.adapter.common.properties.bind.DataBinder;
import com.opages.mvc.adapter.common.properties.bind.RelaxedNames;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, EnvironmentAware {

    public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class.getName();
    private Environment environment;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        ConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(), ConfigurationProperties.class);
        if(annotation != null) {
            MutablePropertyValues propertyValuesForNamePrefix = getPropertyValuesForNamePrefix(annotation.prefix());
            DataBinder db = new DataBinder(bean);
            db.bind(propertyValuesForNamePrefix);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }


    public static void register(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "Registry must not be null");
        if (!registry.containsBeanDefinition(BEAN_NAME)) {
            GenericBeanDefinition definition = new GenericBeanDefinition();
            definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
            definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN_NAME, definition);
        }
    }

    private MutablePropertyValues getPropertyValuesForNamePrefix(String namePrefix) {
        if (!StringUtils.hasText(namePrefix)) {
            return null;
        }
        AbstractEnvironment abstractEnvironment = (AbstractEnvironment)environment;
        List<PropertyValue> propertyValueList = new ArrayList<>();
//        List<PropertyValue> propertyConsulValueList = new ArrayList<>();
        abstractEnvironment.getPropertySources().forEach(ps -> {
            if (ps instanceof EnumerablePropertySource) {
                EnumerablePropertySource<?> eps = (EnumerablePropertySource<?>) ps;
                for (String name : eps.getPropertyNames()) {
                    for (String prefix : new RelaxedNames(stripLastDot(namePrefix))) {
//                        Object source = eps.getSource();
                        for (String separator : new String[]{".", "_"}) {
                            String candidate = (StringUtils.hasLength(prefix) ? prefix + separator : prefix);
                            if (name.startsWith(candidate)) {
                                String propName = name.substring(candidate.length());
                                propertyValueList.add(new PropertyValue(propName, eps.getProperty(name)));
                            }
                        }
                    }
                }
            }
        });
        //排序后根据spring优先级覆盖本地配置文件
        Collections.reverse(propertyValueList);
        return new MutablePropertyValues(propertyValueList);
    }
    private String stripLastDot(String string) {
        if (StringUtils.hasLength(string) && string.endsWith(".")) {
            string = string.substring(0, string.length() - 1);
        }
        return string;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}
